#include "../MainLoop/Application.h"
#include "Scene.h"

#include <vector>
#include <iostream>
#include <string>
#include <glm\glm.hpp>
#include <glm\mat4x4.hpp>
#include <glm\gtc\matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm\gtx\quaternion.hpp>

static const glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);

Scene::Scene()
{
	mcameraPos.x = 5.0f; mcameraPos.y = 3.0f; mcameraPos.z = 5.0f;
	mcameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
	mObjects.localToWorld = new glm::mat4x4[1];
	
	mProjection = glm::perspective(59.0f, 16.0f / 9.0f, 0.1f, 100.0f);
}

void Scene::Initialize()
{
	glGenVertexArrays(1, &mVao);
	glBindVertexArray(mVao);

	// Create the shaders
	mvertexShaderID = glCreateShader(GL_VERTEX_SHADER);
	mfragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

	// Read the Vertex Shader code from the file
	std::string VertexShaderCode;
	std::ifstream VertexShaderStream("../Files/simplevertexshader.vertexshader", std::ios::in);
	if (VertexShaderStream.is_open())
	{
		std::string Line = "";
		while (getline(VertexShaderStream, Line))
			VertexShaderCode += "\n" + Line;
		VertexShaderStream.close();
	}

	// Read the Fragment Shader code from the file
	std::string FragmentShaderCode;
	std::ifstream FragmentShaderStream("../Files/simplefragmentshader.fragmentshader", std::ios::in);
	if (FragmentShaderStream.is_open()){
		std::string Line = "";
		while (getline(FragmentShaderStream, Line))
			FragmentShaderCode += "\n" + Line;
		FragmentShaderStream.close();
	}

	GLint Result = GL_FALSE;
	int InfoLogLength;

	// Compile Vertex Shader
	gpApp->PrintToGlLog("Compiling shader: simplevertexshader.vertexshader\n");
	char const * VertexSourcePointer = VertexShaderCode.c_str();
	glShaderSource(mvertexShaderID, 1, &VertexSourcePointer, NULL);
	glCompileShader(mvertexShaderID);

	// Check Vertex Shader
	glGetShaderiv(mvertexShaderID, GL_COMPILE_STATUS, &Result);
	glGetShaderiv(mvertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
	if (InfoLogLength)
	{
		std::vector<char> VertexShaderErrorMessage(InfoLogLength);
		glGetShaderInfoLog(mvertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
		gpApp->PrintToGlLog('\n'); gpApp->PrintToGlLog(&VertexShaderErrorMessage[0]);
		gpApp->PrintToGlLog('\n');
	}

	// Compile Fragment Shader
	gpApp->PrintToGlLog("Compiling shader: simplefragmentshader.fragmentshader\n");
	char const * FragmentSourcePointer = FragmentShaderCode.c_str();
	glShaderSource(mfragmentShaderID, 1, &FragmentSourcePointer, NULL);
	glCompileShader(mfragmentShaderID);

	// Check Fragment Shader
	glGetShaderiv(mfragmentShaderID, GL_COMPILE_STATUS, &Result);
	glGetShaderiv(mfragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
	if (InfoLogLength)
	{
		std::vector<char> FragmentShaderErrorMessage(InfoLogLength);
		glGetShaderInfoLog(mfragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
		gpApp->PrintToGlLog('\n'); gpApp->PrintToGlLog(&FragmentShaderErrorMessage[0]);
		gpApp->PrintToGlLog('\n');
	}

	// Link the program
	gpApp->PrintToGlLog("Linking Program"); gpApp->PrintToGlLog('\n');
	mprogramID = glCreateProgram();
	glAttachShader(mprogramID, mvertexShaderID);
	glAttachShader(mprogramID, mfragmentShaderID);
	glLinkProgram(mprogramID);

	// Check the program
	glGetProgramiv(mprogramID, GL_LINK_STATUS, &Result);
	glGetProgramiv(mprogramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
	std::vector<char> ProgramErrorMessage(InfoLogLength);
	if (ProgramErrorMessage.size())
	{
		glGetProgramInfoLog(mprogramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
		gpApp->PrintToGlLog(&ProgramErrorMessage[0]); gpApp->PrintToGlLog('\n');
	}

	worldviewprojLocation = glGetUniformLocation(mprogramID, "gworldViewProj");

	glDeleteShader(mvertexShaderID);
	glDeleteShader(mfragmentShaderID);

	LoadObj("../Files/cube.obj");

	//glGenTextures(1, &mTextureID);
	//glBindTexture(GL_TEXTURE_2D, mTextureID);
	//glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, 512, 512);

	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	glFrontFace(GL_CCW);

	// OpenCL stuff.
	cl_int error = 0;
	mKernel = clCreateKernel(gpApp->GetClProgram(), "matrixmultiply", &error);

	mclBuff = new cl_mem[2];
	mclBuff[0] = gpApp->CreateClBuffer(CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, sizeof(glm::mat4x4), glm::value_ptr(mObjects.localToWorld[0]), &error);

	mrotationQt = glm::quat(0.0005f, 0.0f, 1.0f, 0.0f);
	glm::mat4x4 rotation = glm::toMat4(mrotationQt);
	mclBuff[1] = gpApp->CreateClBuffer(CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(glm::mat4x4), glm::value_ptr(rotation), &error);

	error = clSetKernelArg(mKernel, 0, sizeof(mclBuff[0]), &mclBuff[0]);
	error = clSetKernelArg(mKernel, 1, sizeof(mclBuff[1]), &mclBuff[1]);
}

void Scene::Destroy()
{
	glDeleteVertexArrays(1, &mVao);
	//glDeleprogram // shader
}

void Scene::Update( double dTime, double dElapsedTime )
{
	glFinish();

	//mObjects.localToWorld[0] = mObjects.localToWorld[0] * rotation;
	
	//cl_int error;

	//const size_t itemsPerKernel = 4;
	//error = gpApp->EnqueueNDKernel(mKernel, 1, NULL, &itemsPerKernel, NULL, 0, NULL, NULL);
	//error = gpApp->ReadClBuffer(mclBuff[0], CL_TRUE, NULL, sizeof(float)* 16, glm::value_ptr(mObjects.localToWorld[0]), 0, NULL, NULL);
}

void Scene::Render()
{
	cl_uint error = gpApp->FinishClQueue();

	glUseProgram(mprogramID);

	// Index buffer
	//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mindexBuffer);
	//glBindBuffer(GL_ARRAY_BUFFER, mvertexBuffer);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

	//glm::mat4x4 view = glm::lookAt(mcameraPos, mcameraTarget, up);
	glm::mat4x4 vp = mProjection * glm::lookAt(mcameraPos, mcameraTarget, up); // This is the view matrix.
	glm::mat4x4 wvp = vp * mObjects.localToWorld[0];
	glUniformMatrix4fv(worldviewprojLocation, 1, GL_FALSE, glm::value_ptr(wvp));

	// Draw the triangles !
	glDrawElements(
		GL_TRIANGLES,		// mode
		mSize,				// count
		GL_UNSIGNED_INT,	// type
		(void*)0			// element array buffer offset
		);

	//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	//glBindBuffer(GL_ARRAY_BUFFER, 0);

	glUseProgram(0);
}

void Scene::LoadObj(std::string fileName)
{
	std::vector<GLuint> indices;
	std::vector<GLfloat> verts;
	std::ifstream file = std::ifstream(fileName);
	std::string junk;
	int index;
	float loc;
	char type;
	file >> type;

	while (!file.eof())
	{
		switch (type)
		{
			case 'v':
			{
				file >> loc;
				verts.push_back(loc);
				file >> loc;
				verts.push_back(loc);
				file >> loc;
				verts.push_back(loc);

				break;
			}
			case 'f':
			{
				file >> index;
				indices.push_back(index - 1);
				file >> index;
				indices.push_back(index - 1);
				file >> index;
				indices.push_back(index - 1);

				break;
			}
		default:
			break;
		}

		file >> type;
	}

	mSize = indices.size();

	// Generate a buffer for the vertices
	glGenBuffers(1, &mvertexBuffer);
	glBindBuffer(GL_ARRAY_BUFFER, mvertexBuffer);
	glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(GLfloat), &verts[0], GL_STATIC_DRAW);

	// Generate a buffer for the indices
	glGenBuffers(1, &mindexBuffer);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mindexBuffer);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint), &indices[0], GL_STATIC_DRAW);

	mObjects.localToWorld[0] = glm::mat4x4(1.0f);
}